﻿using System;
using System.ComponentModel;
using System.Threading;
using System.Windows.Forms;
using System.Collections.Generic;
using ElWireControl.Sample;
using System.Diagnostics;
using System.Management;


namespace ElWireControl
{
    public partial class Form1 : Form
    {
        // ----------------------------------------------------------------------------- //
        // -------------------------- Variablen / Konstanten --------------------------- //
        // ----------------------------------------------------------------------------- //
        #region Variablen / Konstanten

        // Daten für Sequenzsteuerung
        private static BindingList<Datahandling.ExcelData> data;

        // Musik
        private static Audio.Mci music = new Audio.Mci();
        
        // Kommunikationsport
        private System.IO.Ports.SerialPort serialPort = new System.IO.Ports.SerialPort();
        
        // Timer für GUI-Refresh
        private static System.Windows.Forms.Timer timerActualTime = new System.Windows.Forms.Timer() { Interval = 1000 };
        private static Stopwatch stoppUhr = new Stopwatch();
        private const int CHECKSTATUS_SEC = 30; // überprüft den Status in einem gewissen Intervall. Masseinheit: Sekunden

        // Timer für Sequenzsteuerung
        private static System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer();
        private static int programStart_millisec = 0;
        
        // GUI-Anzeige der Anzüge 
        private BindingSource bsDataGridView = new BindingSource();

        /// <summary>
        /// Offset in Millisek, der zur Startzeit hinzuaddiert wird.
        /// </summary>
        private const int startTimeOffset = 0;

        #endregion Variablen / Konstanten

        // ----------------------------------------------------------------------------- //
        // ----------------------------- Benutzerinterface ----------------------------- //
        // ----------------------------------------------------------------------------- //
        #region Benutzerinterface


        public Form1()
        {
            InitializeComponent();
        }


        private void Form1_Load(object sender, EventArgs e)
        {
            // 1. verfügbare Com-Ports lesen
            ComPorts_Read();

            // 2. Eventhandler initialisieren
            timer.Tick += Timer_Tickhandler;
            timerActualTime.Tick += TimerActualTime_Tickhandler;

            // 3. Geräteinformationen initialisieren
            dgvDaten.AutoGenerateColumns = false;
            dgvDaten.DataSource = data;
            dgvDaten.Columns.Add(new DataGridViewTextBoxColumn { Name = "Name", DataPropertyName = "name", DefaultCellStyle = new DataGridViewCellStyle { Alignment = DataGridViewContentAlignment.TopLeft }, AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill });
            dgvDaten.Columns.Add(new DataGridViewTextBoxColumn { Name = "Geräte-Nr.", DataPropertyName = "address", DefaultCellStyle = new DataGridViewCellStyle { Alignment = DataGridViewContentAlignment.TopRight }, AutoSizeMode = DataGridViewAutoSizeColumnMode.None, Width = 60 });
            dgvDaten.Columns.Add(new DataGridViewProgressColumn { Name = "Akku", DataPropertyName = "battery", DefaultCellStyle = new DataGridViewCellStyle { Alignment = DataGridViewContentAlignment.TopRight }, AutoSizeMode = DataGridViewAutoSizeColumnMode.None, Width = 80 });
            dgvDaten.Columns.Add(new DataGridViewTextBoxColumn { Name = "Signalstärke RSSI", DataPropertyName = "signal_rssi", DefaultCellStyle = new DataGridViewCellStyle { Alignment = DataGridViewContentAlignment.TopRight }, AutoSizeMode = DataGridViewAutoSizeColumnMode.None, Width = 70 });
            dgvDaten.Columns.Add(new DataGridViewTextBoxColumn { Name = "Signalstärke LQI", DataPropertyName = "signal_lqi", DefaultCellStyle = new DataGridViewCellStyle { Alignment = DataGridViewContentAlignment.TopRight }, AutoSizeMode = DataGridViewAutoSizeColumnMode.None, Width = 70 });
            dgvDaten.Columns.Add(new DataGridViewTextBoxColumn { Name = "Sequenzzeile", DataPropertyName = "comment", DefaultCellStyle = new DataGridViewCellStyle { Alignment = DataGridViewContentAlignment.TopLeft }, AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill });

            // 4. Manuelle Steuerung initialisieren
            cbSuitNmbr.SelectedIndex = 0;
            cbOutputHead.SelectedIndex = 0;
            cbOutputBody.SelectedIndex = 0;
            cbOutputLArm.SelectedIndex = 0;
            cbOutputLFoot.SelectedIndex = 0;
            cbOutputLHand.SelectedIndex = 0;
            cbOutputLLeg.SelectedIndex = 0;
            cbOutputRArm.SelectedIndex = 0;
            cbOutputRFoot.SelectedIndex = 0;
            cbOutputRHand.SelectedIndex = 0;
            cbOutputRLeg.SelectedIndex = 0;
        }


        private void Form1_Closing(object sender, FormClosingEventArgs e)
        {
            StopPlaying();
            SerialPortClose();
        }


        private void btLoadData_Click(object sender, EventArgs e)
        {
            OpenFileDialog ofd = new OpenFileDialog();
            ofd.Filter = "Exceldateien |*.xlsx;*.xls";
            ofd.RestoreDirectory = true;


            if (ofd.ShowDialog() != DialogResult.OK)
            {
                MessageBox.Show("keine Datei ausgewählt.");
                return;
            }

            try
            {
                if (ofd.FileName == null)
                    throw new Exception();

                Thread th = new Thread(new ThreadStart(delegate
                {
                    this.Invoke(new EventHandler(delegate
                    {
                        lbReadData.Visible = true;
                        btStart.Enabled = false;
                    }));
                    data = new BindingList<Datahandling.ExcelData>(Datahandling.ReadExcel(ofd.FileName));
                    this.Invoke(new EventHandler(delegate
                    {
                        lbReadData.Visible = false;
                        btStart.Enabled = true;
                        dgvDaten.DataSource = data;
                    }));
                }));
                th.Start();
                
            }
            catch (Exception)
            {
                data = null;
                MessageBox.Show("Fehler: Datei konnte nicht gelesen werden.");
            }
            
        }


        private void btLoadMusic_Click(object sender, EventArgs e)
        {
            OpenFileDialog ofdMusic = new OpenFileDialog();
            ofdMusic.Filter = "Musikdateien (*.mp3, *.wav) |*.mp3;*.wav";
            
            ofdMusic.RestoreDirectory = true;


            if (ofdMusic.ShowDialog() != DialogResult.OK)
            {
                MessageBox.Show("keine Musik ausgewählt.");
                return;
            }

            try
            {
                if (ofdMusic.FileName == null)
                    throw new Exception();

                if (music.IsOpen)
                    music.Close();

                music.Open(ofdMusic.FileName, null);
            }
            catch (Exception)
            {

                MessageBox.Show("Musikdatei konnte nicht gelesen werden.");
            }
        }


        private void btStart_Click(object sender, EventArgs e)
        {
            if (stoppUhr.IsRunning)
            {   // stopp
                StopPlaying();
            }
            else
            {   // start
                // step 0: check serial com
                if (!serialPort.IsOpen)
                {
                    MessageBox.Show("mit keinem Gerät verbunden");
                    return;
                }

                // step 1: check data
                if (data == null || data.Count == 0)
                {
                    MessageBox.Show("keine Programmdaten geladen");
                    return;
                }

                // step 2: check music
                if (!music.IsOpen)
                {
                    if (MessageBox.Show("keine Musik geladen. \r\nFortfahren?","Fehlende Musik", MessageBoxButtons.YesNo) == DialogResult.No)
                    {
                        return;
                    }
                }

                // step 3: Ausgänge aller Geräte abschalten
                if (!SendCmdToAllDevices_SwitchOffOutputs())
                {
                    MessageBox.Show("Die Verbindung zum Gerät wurde unterbrochen");
                    return;
                }

                // step 4: Statusanfrage aller Geräte
                if (!SendCmdToAllDevices_RequestStatus())
                {
                    MessageBox.Show("Die Verbindung zum Gerät wurde unterbrochen");
                    return;
                }


                // step 5: check startpunkt
                programStart_millisec = (int)(dtpStartTime.Value - new DateTime(dtpStartTime.Value.Year, dtpStartTime.Value.Month, dtpStartTime.Value.Day)).TotalMilliseconds;
                if (programStart_millisec >= GetSequenceEnd())
                {
                    MessageBox.Show("Programm nicht gestartet. Startzeit ist grösser als die Sequenzdauer.");
                    return;
                }


                // step 6: Kommunikation starten
                // step 6.1: gehe zu Startpunkt
                SetCmdStartindex(programStart_millisec);

                // step 7: musik starten
                if(music.IsOpen)
                    music.Play(programStart_millisec, false);

                // step 8: starte Sequenzprogramm
                stoppUhr.Reset();
                stoppUhr.Start();
                timer.Tick += Timer_Tickhandler;
                timer.Interval = SetTimerIntervall();
            
                if (timer.Interval >= int.MaxValue)
                    return;

                timer.Start();
                timerActualTime.Start();

                dtpStartTime.Enabled = false;
                btMusic.Enabled = false;
                btConnect.Enabled = false;
                btLoadData.Enabled = false;
                btStart.Text = "&Stopp";
            }
        }


        private void btRefresh_Click(object sender, EventArgs e)
        {
            ComPorts_Read();
        }


        private void btConnect_Click(object sender, EventArgs e)
        {
            if (serialPort.IsOpen)
            {
                // schliesse Port
                SerialPortClose();
                tabControl.Enabled = false;
                return;
            }
            else
            {
                // überprüfe Port
                if (cbComPorts.SelectedItem == null)
                {
                    MessageBox.Show("kein Com-Port vorhanden. Bitte Gerät mit PC verbinden.");
                    return;
                }

                // öffne Port
                if (cbComPorts.SelectedItem.ToString().Length == 0)
                {
                    MessageBox.Show("ungültiger Com-Port.");
                    return;
                }

                // Serielle Verbindung öffnen
                try
                {
                    serialPort = new System.IO.Ports.SerialPort(cbComPorts.SelectedItem.ToString(), 115200, System.IO.Ports.Parity.None, 8);
                    serialPort.Open();
                    serialPort.DataReceived += SerialPort_DataReceived;

                    btConnect.Text = "&Trennen";
                    tabControl.Enabled = true;
                }
                catch (Exception)
                {
                    MessageBox.Show("Com-Port konnte nicht geöffnet werden!");
                    return;
                }
            }
        }


        private void btOutputAllOn_Click(object sender, EventArgs e)
        {
            // Comboboxen setzen
            cbOutputBody.SelectedIndex = 9;
            cbOutputRLeg.SelectedIndex = 9;
            cbOutputLHand.SelectedIndex = 9;
            cbOutputLLeg.SelectedIndex = 9;
            cbOutputRArm.SelectedIndex = 9;
            cbOutputLFoot.SelectedIndex = 9;
            cbOutputHead.SelectedIndex = 9;
            cbOutputRFoot.SelectedIndex = 9;
            cbOutputLArm.SelectedIndex = 9;
            cbOutputRHand.SelectedIndex = 9;

            SendCmdToAllDevices_SwitchOnOutputs();
        }


        private void btOutputAllOff_Click(object sender, EventArgs e)
        {
            // Comboboxen setzen
            cbOutputBody.SelectedIndex = 0; 
            cbOutputRLeg.SelectedIndex = 0; 
            cbOutputLHand.SelectedIndex = 0; 
            cbOutputLLeg.SelectedIndex = 0; 
            cbOutputRArm.SelectedIndex = 0; 
            cbOutputLFoot.SelectedIndex = 0; 
            cbOutputHead.SelectedIndex = 0; 
            cbOutputRFoot.SelectedIndex = 0; 
            cbOutputLArm.SelectedIndex = 0; 
            cbOutputRHand.SelectedIndex = 0;

            SendCmdToAllDevices_SwitchOffOutputs();
        }

        #endregion Benutzerinterface

        // ----------------------------------------------------------------------------- //
        // ------------------------------- Programm Code ------------------------------- //
        // ----------------------------------------------------------------------------- //
        #region Programm Code
        
        
        private void SerialPortClose()
        {
            if (serialPort.IsOpen)
            {
                serialPort.DataReceived -= SerialPort_DataReceived;
                try
                {
                    serialPort.Close();
                }
                catch (Exception)
                {
                    // Verbindung zum Board bereits unterbrochen
                    // --> Exception bei serialPort.Close();
                    //     --> do nothing
                }
                btConnect.Text = "&Verbinden";
            }
            ComPorts_Read();
        }

        /// <summary>
        /// Wird aufgerufen, wenn neue Daten empfangen wurden
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void SerialPort_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
        {
            string rxdata = "";
            string cmd = "";
            while (serialPort.BytesToRead > 0)
            {
                rxdata = serialPort.ReadLine();
                if (rxdata.Length < 2)
                {
                    continue;
                }

                cmd = rxdata.Substring(0,2);
                #region überprüfe Kommando
                switch (cmd)
                {
                    case "as":  // Antwort Statusanfrage
                    case "AS":  // Antwort Statusanfrage
                        #region Antwort zu Statusanfrage
                        if ( rxdata.Length < 4)
	                    {
		                    continue;
	                    }
                       
                        int address = Convert.ToInt32(rxdata.Substring(2,2));
                        string[] payload = rxdata.Split(new char[] { ',' });

                        if (payload.Length < 4)
                        {
                            continue;
                        }

                        // payload[0] = AS[address]
                        // payload[1] = Akkuspannung
                        // payload[2] = RSSI Wert
                        // payload[3] = LQI Wert

                        // erneuere Daten
                        for (int i = 0; i < data.Count; i++)
                        {
                            if (data[i].address != address)
                            {
                                continue;
                            }

                            data[i].battery = payload.Length <= 1 || payload[1].Length <= 0 ? 0 : Convert.ToInt32(payload[1]);
                            //data[i].signal_rssi = (Datahandling.Signalstärke)(payload.Length <= 0 ? 0 : Convert.ToInt32(payload[2]));
                            //data[i].signal_lqi = (Datahandling.Signalstärke)(payload.Length <= 0 ? 0 : Convert.ToInt32(payload[3]));
                            data[i].signal_rssi = (payload.Length <= 2 || payload[2].Length <= 0 ? 0 : Convert.ToInt32(payload[2]));
                            data[i].signal_lqi = (payload.Length <= 3 || payload[3].Length <= 0 ? 0 : Convert.ToInt32(payload[3]));
                        }
                        
                        #endregion Antwort zu Statusanfrage
                        break;

                    default:
                        break;
                }
                #endregion überprüfe Kommando
            }
        }


        private bool SerialPort_DataTransmitting(string data)
        {
            try
            {
                serialPort.Write(data);
            }
            catch (Exception)
            {
                // Verbindung zum Gerät unterbrochen
                SerialPortClose();
                music.Close();
                return false;
            }
            return true;
        }


        private void ComPorts_Read()
        {
            cbComPorts.Items.Clear();
            // verfügbare Com-Ports auslesen
            foreach (var item in System.IO.Ports.SerialPort.GetPortNames())
                cbComPorts.Items.Add(item);

            if (cbComPorts.Items.Count > 0)
                cbComPorts.SelectedIndex = 0;
            else
                cbComPorts.Text = "";
        }


        private void Timer_Tickhandler(object sender, EventArgs e)
        {
            // step 1: init
            timer.Stop();
            int position = (int)stoppUhr.ElapsedMilliseconds + programStart_millisec;

            // step 2: send data
            foreach (var item in data)
            {
                while ((item.contentIndexPointer < item.content.Count) &&
                        (item.content[item.contentIndexPointer].cmdSendtime_ms < position))
                {
                    if (item.address == 3)
                    {
                        // to something
                    }

                    if (!SerialPort_DataTransmitting(item.content[item.contentIndexPointer].cmd))
                    {
                        // Fehler beim Versenden der Daten (Kom-Unterbruch)
                        StopPlaying();
                        MessageBox.Show("Fehler: Keine Verbindung zum Com-Port.\r\nDie Programmsteuerung ist deshalb gestoppt worden.");
                    }
                    item.contentIndexPointer++;
                }
            }


            // step 3: prepare new timer
            timer.Interval = SetTimerIntervall();
            if (timer.Interval < int.MaxValue)
                timer.Start();
            else
            {
                
                // work finished
                // wartet bis Lied fertig gespielt hat
            }
        }


        private bool StopPlaying()
        {
            if (stoppUhr.IsRunning)
            {
                dtpStartTime.Enabled = true;
                btMusic.Enabled = true;
                btConnect.Enabled = true;
                btLoadData.Enabled = true;

                timer.Stop();
                timerActualTime.Stop();
                stoppUhr.Stop();
                if (music.IsOpen)
                    music.Stop();

                btStart.Text = "&Start";
            }

            return SendCmdToAllDevices_SwitchOffOutputs();
        }


        private void TimerActualTime_Tickhandler(object sender, EventArgs e)
        {
            int positionMilliSec = (int)stoppUhr.ElapsedMilliseconds + programStart_millisec;
            tbActualTime.Text = string.Format("{0:mm} : {0:ss}", new TimeSpan(0, 0, 0, 0, positionMilliSec));

            if (music.IsOpen)
            {   // with music
                if (positionMilliSec >= music.Length && positionMilliSec >= GetSequenceEnd())
                    StopPlaying();
            }
            else
            {   // without music
                if (positionMilliSec >= GetSequenceEnd())
                    StopPlaying();
            }

            // Statusabfrage: wird alle [CHECKSTATUS_SEC] überprüft
            int positionSec = positionMilliSec / 1000;
            if (positionSec == positionSec / CHECKSTATUS_SEC * CHECKSTATUS_SEC)
            {
                SendCmdToAllDevices_RequestStatus();
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns>Timerintervall. Wenn Intervall = int.MaxValue, dann konnte kein neues Intervall mehr gefunden werden</returns>
        private int SetTimerIntervall()
        {
            int intervall = int.MaxValue;
            int tmp = 0;
            int position = (int)stoppUhr.ElapsedMilliseconds + programStart_millisec;

            foreach (var item in data)
	        {
                if (item.contentIndexPointer >= item.content.Count)
                    continue;   // skip cmd. this data is finished

		        tmp = item.content[item.contentIndexPointer].cmdSendtime_ms - position;
                if (tmp < intervall)
                     intervall = tmp;
	        }

            return intervall < 1 ? 1 : intervall;
        }


        private bool SendCmdToAllDevices_SwitchOffOutputs()
        {
            if (data != null)
            {
                foreach (var item in data)
                {
                    if (!SerialPort_DataTransmitting(string.Format("FC{0:00}SE0000000000\r\n", item.address)))
                        return false;
                }
            }
            return true;
        }


        private bool SendCmdToAllDevices_SwitchOnOutputs()
        {
            if (data != null)
            {
                foreach (var item in data)
                {
                    if (!SerialPort_DataTransmitting(string.Format("FC{0:00}SE9999999999\r\n", item.address)))
                        return false;
                }
            }
            return true;
        }


        private bool SendCmdToAllDevices_RequestStatus()
        {
            foreach (var item in data)
            {
                if (!SerialPort_DataTransmitting(string.Format("FC{0:00}RS\r\n", item.address)))
                    return false;
            }
            return true;
        }


        private static void SetCmdStartindex(int starttime)
        {
            foreach (var item in data)
            {
                for (item.contentIndexPointer = 0; (item.contentIndexPointer < item.content.Count - 1) && (item.content[item.contentIndexPointer].cmdSendtime_ms < starttime); item.contentIndexPointer++) ;
            }
        }


        private static int GetSequenceEnd()
        {
            int timeMillisec = 1;
            foreach (var item in data)
	        {
		        if (item.sequenceEnd > timeMillisec)
                     timeMillisec = item.sequenceEnd;
	        }
            return timeMillisec;
        }


        private void SelectionChanged(object sender, EventArgs e)
        {
            if (serialPort.IsOpen)
            {
                // prepare commad
                string cmd = string.Format("{0}{1}{2}{3}{4}{5}{6}{7}{8}{9}",
                    cbOutputBody.SelectedIndex > 0 ? cbOutputBody.SelectedIndex: 0,
                    cbOutputRLeg.SelectedIndex > 0 ? cbOutputRLeg.SelectedIndex: 0,
                    cbOutputLHand.SelectedIndex > 0 ? cbOutputLHand.SelectedIndex: 0,
                    cbOutputLLeg.SelectedIndex > 0 ? cbOutputLLeg.SelectedIndex: 0,
                    cbOutputRArm.SelectedIndex > 0 ? cbOutputRArm.SelectedIndex: 0,
                    cbOutputLFoot.SelectedIndex > 0 ? cbOutputLFoot.SelectedIndex: 0,
                    cbOutputHead.SelectedIndex > 0 ? cbOutputHead.SelectedIndex: 0,
                    cbOutputRFoot.SelectedIndex > 0 ? cbOutputRFoot.SelectedIndex: 0,
                    cbOutputLArm.SelectedIndex > 0 ? cbOutputLArm.SelectedIndex : 0,
                    cbOutputRHand.SelectedIndex > 0 ? cbOutputRHand.SelectedIndex : 0);

                // send data
                if (cbSuitNmbr.SelectedIndex > 0)
                {
                    SerialPort_DataTransmitting(string.Format("FC{0:00}SE{1}\r\n", cbSuitNmbr.SelectedIndex, cmd));
                }
                else
                {
                    for (int i = 1; i <= 8; i++)
                    {
                        
                        if (!SerialPort_DataTransmitting(string.Format("FC{0:00}SE{1}\r\n", i, cmd)))
                            break;
                    }
                }
            }
        }
        
        #endregion Programm Code
    }
}
